/* * Copyright (c) 2012. The Genome Analysis Centre, Norwich, UK * MISO project contacts: Robert Davey, Mario Caccamo @ TGAC * ********************************************************************* * * This file is part of MISO. * * MISO is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * MISO is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with MISO. If not, see <http://www.gnu.org/licenses/>. * * ********************************************************************* */ package uk.ac.bbsrc.tgac.miso.notification.core; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.http.HttpMethod; import org.springframework.integration.Message; import org.springframework.integration.channel.ChannelInterceptor; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.interceptor.WireTap; import org.springframework.integration.endpoint.SourcePollingChannelAdapter; import org.springframework.integration.file.filters.CompositeFileListFilter; import org.springframework.integration.handler.LoggingHandler; import org.springframework.integration.handler.MethodInvokingMessageProcessor; import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler; import org.springframework.integration.http.support.DefaultHttpHeaderMapper; import org.springframework.integration.ip.tcp.TcpOutboundGateway; import org.springframework.integration.ip.tcp.connection.TcpNetClientConnectionFactory; import org.springframework.integration.splitter.MethodInvokingSplitter; import org.springframework.integration.support.channel.BeanFactoryChannelResolver; import org.springframework.integration.support.channel.ChannelResolver; import org.springframework.integration.transformer.HeaderEnricher; import org.springframework.integration.transformer.MessageTransformingHandler; import uk.ac.bbsrc.tgac.miso.core.data.type.PlatformType; import uk.ac.bbsrc.tgac.miso.core.service.integration.ws.pacbio.PacBioServiceWrapper; import uk.ac.bbsrc.tgac.miso.core.service.integration.ws.solid.SolidServiceWrapper; import uk.ac.bbsrc.tgac.miso.notification.manager.NotificationRequestManager; import uk.ac.bbsrc.tgac.miso.notification.util.NotificationMessageEnricher; import uk.ac.bbsrc.tgac.miso.notification.util.NotificationUtils; import uk.ac.bbsrc.tgac.miso.tools.run.MultiFileQueueMessageSource; import uk.ac.bbsrc.tgac.miso.tools.run.RunFolderScanner; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.net.URI; import java.util.*; import java.util.concurrent.TimeUnit; /** * uk.ac.bbsrc.tgac.miso.notification.service.impl * <p/> * Info * * @author Rob Davey * @date 06-Dec-2010 * @since 0.1.4 */ public class DefaultNotifier { protected static final Logger log = LoggerFactory.getLogger(DefaultNotifier.class); public static void main(String[] args) { log.info("Starting notification system..."); ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/notification.xml"); ChannelResolver channelResolver = new BeanFactoryChannelResolver(context); NotificationUtils notificationUtils = new NotificationUtils(); File propsPath = new File(".", "notification.properties"); Properties props = new Properties(); try { props.load(new FileReader(propsPath)); CompositeFileListFilter statusFilter = (CompositeFileListFilter) context.getBean("statusFilter"); Map<String, Set<File>> allDataPaths = new HashMap<String, Set<File>>(); for (String platformType : PlatformType.getKeys()) { platformType = platformType.toLowerCase(); if (props.containsKey(platformType + ".splitterBatchSize")) { notificationUtils.setSplitterBatchSize(Integer.parseInt(props.getProperty(platformType + ".splitterBatchSize"))); } if (props.containsKey(platformType + ".dataPaths")) { log.debug("Resolving " + platformType + ".dataPaths ..."); String dataPaths = props.getProperty(platformType + ".dataPaths"); Set<File> paths = new HashSet<File>(); for (String path : dataPaths.split(",")) { File f = new File(path); if (f.exists() && f.canRead() && f.isDirectory()) { paths.add(f); log.debug("Added " + path); } } allDataPaths.put(platformType, paths); if (platformType.equals("solid")) { for (String key : props.stringPropertyNames()) { if (key.startsWith("solid.wsdl.url.")) { String serviceName = key.substring(key.lastIndexOf(".") + 1); log.debug("Creating service: " + serviceName); SolidServiceWrapper ssw = new SolidServiceWrapper(serviceName, URI.create(props.getProperty(key)).toURL()); context.getBeanFactory().registerSingleton(serviceName, ssw); } } } if (platformType.equals("pacbio")) { for (String key : props.stringPropertyNames()) { if (key.startsWith("pacbio.ws.url.")) { String serviceName = key.substring(key.lastIndexOf(".") + 1); log.debug("Creating service: " + serviceName); PacBioServiceWrapper psw = new PacBioServiceWrapper(serviceName, URI.create(props.getProperty(key))); context.getBeanFactory().registerSingleton(serviceName, psw); } } } RunFolderScanner rfs = (RunFolderScanner) context.getBean(platformType + "StatusRecursiveScanner"); MultiFileQueueMessageSource mfqms = new MultiFileQueueMessageSource(); mfqms.setBeanName(platformType + "MultiFileQueueMessageSource"); mfqms.setBeanFactory(context.getBeanFactory()); mfqms.setScanner(rfs); mfqms.setFilter(statusFilter); mfqms.setDirectories(paths); //make sure all the directories are rescanned each poll mfqms.setScanEachPoll(false); mfqms.afterPropertiesSet(); SourcePollingChannelAdapter spca = new SourcePollingChannelAdapter(); spca.setBeanName(platformType + "StatusFileSource"); spca.setBeanFactory(context.getBeanFactory()); spca.setMaxMessagesPerPoll(1); DynamicTrigger trigger; if (props.containsKey(platformType + ".scanRate")) { trigger = new DynamicTrigger(Integer.parseInt(props.getProperty(platformType + ".scanRate")), TimeUnit.MILLISECONDS); } else { trigger = new DynamicTrigger(600000, TimeUnit.MILLISECONDS); } trigger.setFixedRate(false); spca.setTrigger(trigger); spca.setSource(mfqms); spca.setOutputChannel(channelResolver.resolveChannelName(platformType + "StatusFileInputChannel")); spca.setAutoStartup(false); spca.afterPropertiesSet(); DirectChannel outputChannel = (DirectChannel) channelResolver.resolveChannelName(platformType + "StatusChannel"); outputChannel.setBeanName(platformType + "StatusChannel"); outputChannel.setBeanFactory(context.getBeanFactory()); if (props.containsKey("wiretap.enabled") && "true".equals(props.get("wiretap.enabled"))) { //set up wire tap DirectChannel wireTapChannel = (DirectChannel) channelResolver.resolveChannelName("wireTapChannel"); wireTapChannel.setBeanName("wireTapChannel"); wireTapChannel.setBeanFactory(context.getBeanFactory()); LoggingHandler wireTapLogger = new LoggingHandler("TRACE"); wireTapLogger.setBeanName("OutputWireTapper"); wireTapLogger.setBeanFactory(context.getBeanFactory()); wireTapLogger.setLoggerName("wiretap"); wireTapLogger.setShouldLogFullMessage(true); wireTapLogger.afterPropertiesSet(); wireTapChannel.subscribe(wireTapLogger); List<ChannelInterceptor> ints = new ArrayList<ChannelInterceptor>(); WireTap wt = new WireTap(wireTapChannel); ints.add(wt); outputChannel.setInterceptors(ints); } DirectChannel signChannel = (DirectChannel)channelResolver.resolveChannelName(platformType+"MessageSignerChannel"); signChannel.setBeanFactory(context.getBeanFactory()); DirectChannel splitterChannel = (DirectChannel)channelResolver.resolveChannelName(platformType+"SplitterChannel"); splitterChannel.setBeanFactory(context.getBeanFactory()); if (props.containsKey(platformType + ".http.statusEndpointURIs")) { log.debug("Resolving " + platformType + ".http.statusEndpointURIs ..."); String statusEndpointURIs = props.getProperty(platformType + ".http.statusEndpointURIs"); for (String uri : statusEndpointURIs.split(",")) { //split into multiple messages MethodInvokingSplitter mis = new MethodInvokingSplitter(notificationUtils, "splitMessage"); mis.setBeanName(platformType + "Splitter"); mis.setBeanFactory(context.getBeanFactory()); mis.setChannelResolver(channelResolver); mis.setOutputChannel(signChannel); splitterChannel.subscribe(mis); //sign messages and inject url into message headers via HeaderEnricher Map<String, SignedHeaderValueMessageProcessor<String>> urlHeaderToSign = new HashMap<String, SignedHeaderValueMessageProcessor<String>>(); URI su = URI.create(uri); urlHeaderToSign.put("x-url", new SignedHeaderValueMessageProcessor<String>(su.getPath())); urlHeaderToSign.put("x-user", new SignedHeaderValueMessageProcessor<String>("notification")); NotificationMessageEnricher signer = new NotificationMessageEnricher(urlHeaderToSign); signer.setMessageProcessor(new MethodInvokingMessageProcessor(notificationUtils, "signMessageHeaders")); MessageTransformingHandler mth = new MessageTransformingHandler(signer); mth.setBeanName(platformType + "Signer"); mth.setBeanFactory(context.getBeanFactory()); mth.setChannelResolver(channelResolver); mth.setOutputChannel(outputChannel); mth.setRequiresReply(false); signChannel.subscribe(mth); DefaultHttpHeaderMapper hm = new DefaultHttpHeaderMapper(); hm.setUserDefinedHeaderPrefix(""); String[] names = {"Accept", "x-url", "x-signature", "x-user"}; hm.setBeanFactory(context.getBeanFactory()); hm.setOutboundHeaderNames(names); hm.setInboundHeaderNames(names); HttpRequestExecutingMessageHandler statusNotifier = new HttpRequestExecutingMessageHandler(uri); statusNotifier.setBeanName(platformType + "StatusNotifier"); statusNotifier.setBeanFactory(context.getBeanFactory()); statusNotifier.setChannelResolver(channelResolver); statusNotifier.setHttpMethod(HttpMethod.POST); statusNotifier.setCharset("UTF-8"); statusNotifier.setHeaderMapper(hm); statusNotifier.setExtractPayload(true); statusNotifier.setOrder(3); statusNotifier.setRequiresReply(false); statusNotifier.setExpectReply(false); outputChannel.subscribe(statusNotifier); } } if (props.containsKey(platformType + ".tcp.host") && props.containsKey(platformType + ".tcp.port")) { String host = props.getProperty(platformType + ".tcp.host"); int port = Integer.parseInt(props.getProperty(platformType + ".tcp.port")); TcpNetClientConnectionFactory tnccf = new TcpNetClientConnectionFactory(host, port); tnccf.setSoTimeout(10000); tnccf.setSingleUse(true); TcpOutboundGateway tog = new TcpOutboundGateway(); tog.setBeanName(platformType + "StatusNotifier"); tog.setBeanFactory(context.getBeanFactory()); tog.setChannelResolver(channelResolver); tog.setConnectionFactory(tnccf); tog.setRequestTimeout(10000); tog.setRequiresReply(false); tog.setOutputChannel(outputChannel); outputChannel.subscribe(tog); } if ((!props.containsKey(platformType + ".tcp.host") || !props.containsKey(platformType + ".tcp.port")) && !props.containsKey(platformType + ".http.statusEndpointURIs")) { log.error("You have specified a list of paths to scan, but no valid endpoint to notify. Please add a <platform>.http.statusEndpointURIs property and/or <platform>.tcp.host/port properties in notification.properties"); } else { spca.start(); } } else { log.warn("You have not specified a list of " + platformType + " paths to scan. Please add a " + platformType.toLowerCase() + ".dataPaths property in notification.properties if you wish to track this platform"); } NotificationRequestManager nrm = (NotificationRequestManager)context.getBean("notificationRequestManager"); nrm.setApplicationContext(context); nrm.setDataPaths(allDataPaths); } } catch (FileNotFoundException e) { log.error("Cannot find a notification.properties file in the same directory as the notification jar. Please add one"); } catch (IOException e) { log.error("Cannot read notification.properties. Please check permissions/availability"); //e.printStackTrace(); } catch(Exception e) { log.error("Something else went wrong:"+e.getMessage()); } } static class SignedHeaderValueMessageProcessor<T> implements HeaderEnricher.HeaderValueMessageProcessor<T> { // null indicates no explicit setting; use header-enricher's 'default-overwrite' value private volatile Boolean overwrite = null; private final T value; public SignedHeaderValueMessageProcessor(T value) { this.value = value; } public T processMessage(Message<?> message) { return this.value; } public void setOverwrite(Boolean overwrite) { this.overwrite = overwrite; } public Boolean isOverwrite() { return this.overwrite; } } }